#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <netinet/ether.h>
#include <netinet/if_ether.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netpacket/packet.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>

#define PDEBUG(format, args...) fprintf(stderr, "DEBUG:%s:%d " format, __FILE__, __LINE__, ## args) 

enum _param { DEFAULT = 90 };
//enum _param PARAM;

struct param
{
	char* 			iface;
	int 			flags;
	unsigned long	packet_delay;
};

struct interface
{
	int 			index;
	int				mtu;
	int				metric;
	short			flags;
	struct in_addr	ip;
	struct in_addr	netmask;
	struct in_addr	broadcast;
	struct ether_addr	mac;
} interface;

struct network
{
	unsigned long start, end;
} network;

//////////////////////////////////////////////////////////////////////
struct ifreq iface;
struct param param;

void iface_init(int socket, char* name)
{
	PDEBUG("iface_init() started\n");
	memset(&iface, 0, sizeof(struct ifreq));
	strncpy(iface.ifr_name, name, strlen(name));
	if (ioctl(socket, SIOCGIFINDEX, &iface) == -1) 
		interface.index = -1;
	else {
		interface.index = iface.ifr_ifindex;
		if (ioctl(socket, SIOCGIFADDR, &iface) != -1)
			//memcpy(&iface_ip, &(((struct sockaddr_in * )&iface.ifr_addr)->sin_addr), 4);
			interface.ip = ((struct sockaddr_in * )&iface.ifr_addr)->sin_addr;
		if (ioctl(socket, SIOCGIFNETMASK, &iface) != -1)
			//memcpy(&iface_nmask, &(((struct sockaddr_in * )&iface.ifr_netmask)->sin_addr), 4);
			interface.netmask = ((struct sockaddr_in * )&iface.ifr_netmask)->sin_addr;
		if (ioctl(socket, SIOCGIFBRDADDR, &iface) != -1)
			//memcpy(&iface_bcast, &(((struct sockaddr_in * )&iface.ifr_broadaddr)->sin_addr), 4);
			interface.broadcast = ((struct sockaddr_in * )&iface.ifr_broadaddr)->sin_addr;
		if (ioctl(socket, SIOCGIFHWADDR, &iface) != -1)
			memcpy((void*)interface.mac.ether_addr_octet, (void*)&iface.ifr_hwaddr.sa_data, 6);
		if (ioctl(socket, SIOCGIFMTU, &iface) != -1)
			interface.mtu = iface.ifr_mtu;
		if (ioctl(socket, SIOCGIFFLAGS, &iface) != -1)
			interface.flags = iface.ifr_flags;
		if (ioctl(socket, SIOCGIFMETRIC, &iface) != -1)
			interface.metric = iface.ifr_metric; 
	}	
	PDEBUG("iface_init() stopped\n");
}

int inline iface_get_index()
{
	return (interface.index);
}

int inline iface_get_mtu()
{
	return (interface.mtu);
}

int inline iface_get_metric()
{
	return (interface.metric);
}

short inline iface_get_flags()
{
	return (interface.flags);
}

void inline iface_get_ip(struct in_addr *ip)
{
	(*ip) = interface.ip;
}

void inline iface_get_netmask(struct in_addr *netmask)
{
	(*netmask) = interface.netmask;
}

void inline iface_get_broadcast(struct in_addr *broadcast)
{
	(*broadcast) = interface.broadcast;
}

void inline iface_get_mac(struct ether_addr *mac)
{
	(*mac) = interface.mac;
}


void inline net_calc_network(struct in_addr ip, struct in_addr netmask)
{
	network.start = ip.s_addr & netmask.s_addr; /* sitas isvis niekam nereikalingas, reikia sugalvoti kur i kisti */
	network.end = ip.s_addr | (~netmask.s_addr);
}

in_addr_t net_next_ip(struct in_addr curr_ip)
{
	unsigned long curr;
	curr = ntohl(curr_ip.s_addr);
	if (curr + 1 <= ntohl(network.end)) {
		curr++;
		return (htonl(curr));
	} else return (0);
}

void arp_usage(char* pname)
{
	fprintf(stderr, "Cara pakai: %s arp <options>\n"
					"options:\n"
					"-i <if> 	Network interface\n"
					"-t <time> 	Delay between packets\n"
					"-h  		Help\n",
					pname);
	exit(0);
}

void arp_generate_packet(struct in_addr, struct in_addr, struct ether_addr, u_int8_t*);

int arp_init(int argc, char** argv)
{
	PDEBUG("arp_init called\n");
	//gak extern lagi
	//extern struct param param;
	struct in_addr sip, tip, netmask, tmp;
	int sock_fd;
/**
ll = link level
sockaddr_ll is a device independent physical layer address
struct sockaddr_ll {
*	sll_addr	unsigned char[8]	phy layer addr
*	sll_family	unsigned short		AF_PACKET
*	sll_halen	unsigned char		len of addr
**	sll_hatype	unsigned short		header type
**	sll_pkttype	unsigned char		pkt type
				PACKET_{HOST,BROADCAST,MULTICAST,OTHERHOST,OUTGOING}
***	sll_protocol	unsigned short	phy layer proto
};
total 6, ingat unsigned short itu aku jadi ingat:
bitset<numerical_limits<unsigned short>::digits>(245)
pertama mari kenalan dg header <linux/if_ether.h>
sll_protocol adlh standard ethernet protocol type in net order.
header type: sll_hatype adlh arp type yg didefinisikan di <linux/if_arp.h>

utk kirim kita cukup oprek field yg di * PLUS sll_ifindex.
utk terima cukup field yang di **.
utk bind, cukup field *** dan sll_ifindex.

***/
	struct sockaddr_ll saddr_ll;
	socklen_t sa_size = sizeof(struct sockaddr_ll);
	struct ether_addr mac;
	struct ether_arp* arp_header;
	u_int8_t packet[sizeof(struct ether_header) + sizeof(struct ether_arp)];

	if ( (sock_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ARP))) == -1 ) {
		PDEBUG("Error: gagal socket().\n");
		fprintf(stderr, "Error: tidak bisa buat socket.\n");
		exit(1);
	}
	if ( fcntl(sock_fd, F_SETFL, O_NONBLOCK) == -1 ) {
		PDEBUG("Error: gagal fcntl().\n");
		fprintf(stderr, "Error: tidak bisa set socket ke non-blocking.\n");
		close(sock_fd);
		exit(1);
	}
	iface_init(sock_fd, param.iface);
	if ( (saddr_ll.sll_ifindex = iface_get_index()) == -1 ) {
		fprintf(stderr, "Error: tidak ada divais %s.\n", param.iface);
		close(sock_fd);
		exit(1);
	}
	iface_get_ip(&sip);
	iface_get_netmask(&netmask);
	iface_get_mac(&mac);
	saddr_ll.sll_family = PF_PACKET;
	saddr_ll.sll_halen = ETH_ALEN;
	memset(&saddr_ll.sll_addr, 0, ETH_ALEN);
	net_calc_network(sip, netmask);
	tip.s_addr = sip.s_addr & netmask.s_addr;
	arp_header = (struct ether_arp*)(packet + sizeof(struct ether_header));
	fprintf(stdout, "Discovering network with %s/%s\n\n", inet_ntoa(sip), ether_ntoa(&mac));
	while ( (tip.s_addr = net_next_ip(tip)) != 0 ) {
		memset(packet, 0, sizeof(packet));
		arp_generate_packet(sip, tip, mac, packet);
		sendto(sock_fd, &packet, 42, 0, (struct sockaddr*)& saddr_ll, sizeof(struct sockaddr_ll));
		usleep(param.packet_delay * 100);
		if (recvfrom(sock_fd, &packet, 42, 0, (struct sockaddr*)& saddr_ll, &sa_size) == 42) {
			if (arp_header->arp_op == htons(ARPOP_REPLY)) {
				memcpy((void*)&tmp, (void*)&arp_header->arp_spa, 4);
				fprintf(stdout, "%-15s %s\n", 
					inet_ntoa(tmp), 
					ether_ntoa((struct ether_addr*)&arp_header->arp_sha)
				);
			}
		}
	}
	return 0;
}

void arp_generate_packet(struct in_addr sip, struct in_addr tip, struct ether_addr smac, u_int8_t* packet)
{
	struct ether_header* eth_header;
	struct ether_arp* arp_header;
	eth_header = (struct ether_header*)packet;
	memset((void*)&eth_header->ether_dhost, 0xff, ETH_ALEN);
	memcpy((void*)&eth_header->ether_shost, (void*)&smac.ether_addr_octet, ETH_ALEN);
	eth_header->ether_type = htons(ETHERTYPE_ARP);
	arp_header = (struct ether_arp*)(packet + sizeof(struct ether_header));
	arp_header->arp_hrd = htons(ARPHRD_ETHER);
	arp_header->arp_pro = htons(ETHERTYPE_IP);
	arp_header->arp_hln = ETH_ALEN;
	arp_header->arp_pln = 4;
	arp_header->arp_op = htons(ARPOP_REQUEST);
	memcpy((void*)arp_header->arp_sha, (void*)&smac.ether_addr_octet, ETH_ALEN);
	memcpy((void*)arp_header->arp_spa, (void*)&sip.s_addr, 4);
	memcpy((void*)arp_header->arp_tpa, (void*)&tip.s_addr, 4);
}

int process_options(int argc, char** argv) 
{
	extern char* optarg;
	extern int opterr, optind, optopt;
	int ch;
	optind = 2;
	//param.packet_delay = PARAM.DEFAULT;
	param.packet_delay = DEFAULT;

	while ( (ch = getopt(argc, argv, "i:t:h")) != -1 ) {
		switch(ch) {
		case 'i':
			param.iface = optarg;
			break;
		case 't':
			param.packet_delay = strtol(optarg, (char**)NULL, 10);
			if (param.packet_delay == LONG_MIN || param.packet_delay 
				== LONG_MAX) {
				fprintf(stderr, "Error: delay paket tidak benar, gunakan default = 90s.\n");
				param.packet_delay = DEFAULT;
			}
			break;
		case 'h':
			param.flags |= 0xffffFFFF;
			break;
		default:
			return -1;
			break;
		}
	}
	if (optind - 2 == 0) {
		fprintf(stderr, "Error: argumen kurang dari 2.\n");
		return -1;
	}
	return 0;
}

#ifdef STANDALONE
void usage(char*);
void sighandler(int);

int main(int argc, char** argv)
{
	if (argc < 2)
		usage(argv[0]);

	signal(SIGQUIT, sighandler);
	signal(SIGTERM, sighandler);
	signal(SIGINT, sighandler);

	if (process_options(argc, argv) == -1)
		usage(argv[0]);
	if (strncmp(argv[1], "arp", 3) == 0) {
		if ( (param.flags & 0xffffFFFF) == 0xFFFFffff )
			arp_usage(argv[0]);
		return (arp_init(argc, argv));
	}
	else
		usage(argv[0]);

	return 0;
}

void usage(char* pname)
{
	fprintf(stderr, "Cara pakai: %s <tipe> <options>\n"
					"tipe: arp, wmi, ping, snmp (yang didukung saat ini: arp)\n"
					"options: -h help\n",
					pname);
	exit(0);
}

void sighandler(int type)
{
	exit(1);
}
#endif



